/*******************************************************************************
 * @file fdt_net.c
 * @brief  
 *
 *
 * Author: Kevinx
 *
 * Copyright (c) 2016 Kevinx. All Rights Reserved.
 *
 * CONFIDENTIALITY AND LIMITED USE
 *
 * This software, including any software of third parties embodied herein,
 * contains information and concepts which are confidential to Kevinx
 * and such third parties. This software is licensed for use
 * solely in accordance with the terms and conditions of the applicable
 * license agreement with Kevinx or its authorized distributor.
 ************************************************************************************/
#include "fdt_net.h"

static void sock_close(struct fdt_socket* sock)
{
    close(sock->fd);
}

static int bind_local_address(struct fdt_socket* sock, struct sockaddr* addr, socklen_t addrlen)
{
    int ret;

    ret = bind(sock->fd, addr, addrlen);
    if (ret == -1) {
        printf("bind() failed on fd=%d with error[%d]='%s'",
                sock->fd,
                errno,
                strerror(errno));

        return -1;
    }

    return 0; 
}

static int fdt_listen(struct fdt_socket* sock)
{
    int ret;

    ret = listen(sock->fd, 5); 
    if (ret == -1) {
        printf("listen() failed on fd=%d with error[%d]='%s'",
                sock->fd,
                errno,
                strerror(errno));

        return -1;
    } 

    return 0;
}

static int fdt_connect(struct fdt_socket* sock, struct sockaddr* addr, socklen_t len)
{
    int ret;

    do {
        ret = connect(sock->fd, 
                      addr, 
                      len);
    } while (ret < 0 && errno == EINTR);

    return 0;
}

static int fdt_poll(struct fdt_socket* sock, struct timeval* tv)
{
    int rc;
    fd_set rfds;

    FD_ZERO(&rfds);
    FD_SET(sock->fd, &rfds);

    do {
        rc = select(sock->fd+1, &rfds, 0, 0, tv); 
    } while (rc < 0 && errno == EINTR);

    if (rc > 0)
       if (likely(FD_ISSET(sock->fd,  &rfds))) {
            return 1;
       } else
            return -1;
    else 
        return rc; /* rc <= 0*/
}

static int fdt_listen_noop(struct fdt_socket* sock __attribute__((unused)))
{
    return -1;
}

static int fdt_connect_noop(struct fdt_socket* sock __attribute__((unused)), 
                            struct sockaddr* addr __attribute__((unused)),
                            socklen_t len  __attribute__((unused)))
{
    return -1;
}

static struct fdt_socket* fdt_accept_noop(struct fdt_socket* sock __attribute__((unused)),
                                          struct sockaddr* addr __attribute__((unused)),
                                          socklen_t *addrlen __attribute__((unused)))
{
    return NULL;
}

static int fdt_bind_noop(struct fdt_socket* sock __attribute__((unused)), 
                        struct sockaddr* addr __attribute__((unused)),
                        socklen_t addrlen  __attribute__((unused)))
{
    return -1;
}

static ssize_t recv_msg_noop(struct fdt_socket* sock __attribute__((unused)), 
                            void *buf __attribute__((unused)),
                            size_t len __attribute__((unused)))
{
    return -1;
}

static ssize_t send_msg_noop(struct fdt_socket* sock __attribute__((unused)), 
                            const void *buf __attribute__((unused)),
                            size_t len __attribute__((unused)))
{
    return -1;
}

static int tcp_open_noop(struct fdt_socket* sock __attribute__((unused)), 
                         int flags __attribute__((unused)))
{
    return -1;
}

static int tcp_open(struct fdt_socket* sock, int flags)
{
    if ((sock->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
        return -1;
    }

    int oflags;
    if ((oflags = fcntl(sock->fd, F_GETFL, 0)) == -1) {
        printf("fcntl F_GETFL: errno=[%d:%s]\n", errno, strerror(errno));
        return -1;
    }

    if (fcntl(sock->fd, F_SETFL, oflags|flags) == -1) {
        printf("fcntl F_SETFL: errno=[%d:%s]\n", errno, strerror(errno));
        return -1;
    }

    return 0;
}

static ssize_t tcp_recv_msg(struct fdt_socket* sock, void *buf, size_t len)
{
    int nbytes;

    do {
        nbytes = recv(sock->fd, buf, len, 0);
    } while ((nbytes == -1) && (errno == EINTR));    

    return nbytes;
}

static ssize_t tcp_send_msg(struct fdt_socket* sock, const void *buf, size_t len)
{
    int nbytes;

    do {
        nbytes = send(sock->fd, buf, len, 0);
    } while ((nbytes == -1) && (errno == EINTR));  

    return nbytes;  
}

static struct fdt_sock_ops tcp_sock_ops = {
                .open    = tcp_open,
                .close   = sock_close,
                .connect = fdt_connect,
                .bind    = bind_local_address,
                .listen  = fdt_listen_noop,
                .accept  = fdt_accept_noop,
                .recvive = tcp_recv_msg,
                .send    = tcp_send_msg,
                .poll    = fdt_poll
};

static struct fdt_sock_ops tcp_sock_passive_ops = {
                .open    = tcp_open_noop,
                .close   = sock_close,
                .connect = fdt_connect_noop,
                .bind    = fdt_bind_noop,
                .listen  = fdt_listen_noop,
                .accept  = fdt_accept_noop,
                .recvive = tcp_recv_msg,
                .send    = tcp_send_msg,
                .poll    = fdt_poll
};

static int udp_open(struct fdt_socket* sock, int flags)
{
    if ((sock->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
        return -1;
    }

    int oflags;
    if ((oflags = fcntl(sock->fd, F_GETFL, 0)) == -1) {
        printf("fcntl F_GETFL: errno=[%d:%s]\n", errno, strerror(errno));
        return -1;
    }

    if (fcntl(sock->fd, F_SETFL, oflags|flags) == -1) {
        printf("fcntl F_SETFL: errno=[%d:%s]\n", errno, strerror(errno));
        return -1;
    }

    return 0;
}

static ssize_t udp_recv_msg(struct fdt_socket* sock, void *buf, size_t len)
{
    int nbytes;

    do {
        nbytes = recvfrom(sock->fd, 
                          buf, 
                          len, 
                          0,
                          &sock->addr, 
                          &sock->addrlen);
    } while ((nbytes == -1) && (errno == EINTR));    

    return nbytes;
}

static ssize_t udp_send_msg(struct fdt_socket* sock, const void *buf, size_t len)
{
    int nbytes;

    do {
        nbytes = sendto(sock->fd, 
                        buf, 
                        len, 
                        0, 
                        (struct sockaddr*)&sock->addr, 
                        sock->addrlen);
    } while ((nbytes == -1) && (errno == EINTR));  

    return nbytes;  
}

static struct fdt_sock_ops udp_sock_ops = {
                .open    = udp_open,
                .close   = sock_close,
                .connect = fdt_connect,
                .bind    = bind_local_address,
                .listen  = fdt_listen_noop, 
                .accept  = fdt_accept_noop,               
                .recvive = udp_recv_msg,
                .send    = udp_send_msg,
                .poll    = fdt_poll
};

static int tcp_server_open(struct fdt_socket* sock, int flags)
{
    int ret;

    sock->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    struct linger lingerbuf;  
    lingerbuf.l_onoff = 0; 
    lingerbuf.l_linger = 0; 

    ret = setsockopt(sock->fd, SOL_SOCKET, SO_LINGER, (char*)&lingerbuf, sizeof(lingerbuf));
    if (ret == -1) {
        printf("setsockopt() for SO_LINGER failed on fd=%d with error[%d]='%s'",
                sock->fd,
                errno,
                strerror(errno));

        return -1;
    }

    int reuseval = 1; 
    ret = setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuseval, sizeof(reuseval));
    if (ret == -1) {
        printf("setsockopt() for SO_REUSEADDR failed on fd=%d with error[%d]='%s'",
                sock->fd,
                errno,
                strerror(errno));

        return -1;
    }

    int oflags;
    if ((oflags = fcntl(sock->fd, F_GETFL, 0)) == -1) {
        printf("fcntl F_GETFL: errno=[%d:%s]\n", errno, strerror(errno));
        return -1;
    }

    if (fcntl(sock->fd, F_SETFL, oflags|flags) == -1) {
        printf("fcntl F_SETFL: errno=[%d:%s]\n", errno, strerror(errno));
        return -1;
    }
    
    return 0;
}

static struct fdt_socket* tcp_ser_accept(struct fdt_socket* sock, struct sockaddr* addr, socklen_t *addrlen)
{
    int fd;
    struct fdt_socket* c;

    fd = accept(sock->fd, addr, addrlen);
    if (fd <= 0) {
        printf("accept() failed on fd=%d with error[%d]='%s'",
                sock->fd,
                errno,
                strerror(errno));

        return NULL;
    }

    c = (struct fdt_socket*)malloc(sizeof(struct fdt_socket));

    c->fd = fd;
    c->ops = &tcp_sock_passive_ops;
    
    return c;    
}


static struct fdt_sock_ops tcp_ser_ops = {
                .open    = tcp_server_open,
                .close   = sock_close,
                .connect = fdt_connect_noop,
                .bind    = bind_local_address,
                .listen  = fdt_listen,
                .accept  = tcp_ser_accept,
                .recvive = recv_msg_noop,
                .send    = send_msg_noop,
                .poll    = fdt_poll
};

void make_tcp_srv_socket(struct fdt_socket* sock)
{
    sock->ops = &tcp_ser_ops;
}

void make_tcp_socket(struct fdt_socket* sock)
{
    sock->ops = &tcp_sock_ops;
}

void make_udp_socket(struct fdt_socket* sock, struct sockaddr* daddr, socklen_t addrlen)
{
    sock->ops = &udp_sock_ops;

    if (daddr != NULL) {
        memcpy(&sock->addr, daddr, addrlen);
        sock->addrlen = addrlen;
    }
}

